#ifndef _MOE_ZDT1
#define _MOE_ZDT1

#include "problems/RealArraySolution.hh"
#include "problems/IntArraySolution.hh"
#include <math.h>

class ZDT1 {
private:

    static double f(RealArraySolution *solution) {
        return solution->get(0);
    }

    static double g(RealArraySolution *solution) {

        double sum = 0.0;

        for (int i = 1; i < RealArraySolution::getSize(); i++)
            sum += solution->get(i);

        return 1 + 9*sum/(RealArraySolution::getSize() -1);
    }

    static double h(RealArraySolution *solution) {
        return 1 - sqrt(f(solution) / g(solution));
    }

    ZDT1() {}

public:

    virtual ~ZDT1() {}

    static const Direction OBJECTIVE_DIRECTION_1 = MINIMIZE;

    static const Direction OBJECTIVE_DIRECTION_2 = MINIMIZE;


    static double objectiveFunction1(Solution *solution) {
        return f((RealArraySolution *)solution);
    }

    static double objectiveFunction2(Solution *solution) {
        return g((RealArraySolution *)solution)*h((RealArraySolution *)solution);
    }
};

class ZDT2 {
private:

    static double f(RealArraySolution *solution) {
        return (double)solution->get(0);
    }

    static double g(RealArraySolution *solution) {

        double sum = 0.0;

        for (int i = 1; i < RealArraySolution::getSize(); i++)
            sum += solution->get(i);

        return 1 + 9*sum/(RealArraySolution::getSize() -1);
    }

    static double h(RealArraySolution *solution) {
        double value = f(solution) / g(solution);
        return 1 - (value*value);
    }

    ZDT2() {}

public:

    virtual ~ZDT2() {}

    static const Direction OBJECTIVE_DIRECTION_1 = MINIMIZE;

    static const Direction OBJECTIVE_DIRECTION_2 = MINIMIZE;


    static double objectiveFunction1(Solution *solution) {
        return f((RealArraySolution *)solution);
    }

    static double objectiveFunction2(Solution *solution) {
        return g((RealArraySolution *)solution)*h((RealArraySolution *)solution);
    }
};


class ZDT3 {
private:

    static double f(RealArraySolution *solution) {
        return (double)solution->get(0);
    }

    static double g(RealArraySolution *solution) {

        double sum = 0.0;

        for (int i = 1; i < RealArraySolution::getSize(); i++)
            sum += solution->get(i);

        return 1 + 9*sum/(RealArraySolution::getSize() -1);
    }

    static double h(RealArraySolution *solution) {
        double value = f(solution) / g(solution);
        return 1 - sqrt(value) - value * sin(10 * M_PI * f(solution));
    }

    ZDT3() {}

public:

    virtual ~ZDT3() {}

    static const Direction OBJECTIVE_DIRECTION_1 = MINIMIZE;

    static const Direction OBJECTIVE_DIRECTION_2 = MINIMIZE;


    static double objectiveFunction1(Solution *solution) {
        return f((RealArraySolution *)solution);
    }

    static double objectiveFunction2(Solution *solution) {
        return g((RealArraySolution *)solution)*h((RealArraySolution *)solution);
    }
};


class ZDT4 {
private:

    static double f(RealArraySolution *solution) {
        return solution->get(0);
    }

    static double g(RealArraySolution *solution) {

        double sum = 0.0;

        for (int i = 1; i < RealArraySolution::getSize(); i++) {
            double x_i = solution->get(i);
            sum += (x_i*x_i) - 10 * cos(4 * M_PI * x_i);
        }

        return 1 + 10 * (RealArraySolution::getSize() - 1) + sum;
    }

    static double h(RealArraySolution *solution) {
        double value = f(solution) / g(solution);
        return 1 - sqrt(value);
    }

    ZDT4() {}

public:

    virtual ~ZDT4() {}

    static const Direction OBJECTIVE_DIRECTION_1 = MINIMIZE;

    static const Direction OBJECTIVE_DIRECTION_2 = MINIMIZE;


    static double objectiveFunction1(Solution *solution) {
        return f((RealArraySolution *)solution);
    }

    static double objectiveFunction2(Solution *solution) {
        return g((RealArraySolution *)solution)*h((RealArraySolution *)solution);
    }
};

class ZDT5 {

private:

    static int u(IntArraySolution *solution, int variable) {

        int begin, end = 0;

        if (variable == 0) {

            begin = 0;
            end = 30;

        } else {

            begin = 30 + 5 * (variable - 1);
            end = 30 + 5 * variable;
        }

        int count = 0;

        for (int  i = begin; i < end; i++)
            count += solution->get(i);

        return count;
    }

    static int v(IntArraySolution *solution, int variable) {


        int u_i = u(solution, variable);

        if (u_i == 5) return 1;

        else return 2 + u_i;
    }


    static int f(IntArraySolution *solution) {
        return 1 + u(solution, 0);
    }

    static int g(IntArraySolution *solution) {

        int count = 0;

        for (int i = 1; i < 11; i++) count += v(solution, i);

        return count;

    }

    ZDT5() {}

public:

    virtual ~ZDT5() {}

    static const Direction OBJECTIVE_DIRECTION_1 = MINIMIZE;

    static const Direction OBJECTIVE_DIRECTION_2 = MINIMIZE;


    static double objectiveFunction1(Solution *solution) {
        return (double)f((IntArraySolution *)solution);
    }

    static double objectiveFunction2(Solution *solution) {
        return (double)g((IntArraySolution *)solution) /
               (double)f((IntArraySolution*)solution);
    }

};

class ZDT6 {
private:

    static double f(RealArraySolution *solution) {
        double x_1 = solution->get(0);
        double value = sin(4 * M_PI * x_1);
        return 1 - exp(-4 * x_1) * pow(value, 6);
    }

    static double g(RealArraySolution *solution) {

        double sum = 0.0;

        for (int i = 1; i < RealArraySolution::getSize(); i++)
            sum += solution->get(i);

        return 1 + 9 * pow(sum/(RealArraySolution::getSize() -1), 0.25);
    }

    static double h(RealArraySolution *solution) {
        double value = f(solution) / g(solution);
        return g(solution)*(1 - (value*value));
    }

    ZDT6() {}

public:

    virtual ~ZDT6() {}

    static const Direction OBJECTIVE_DIRECTION_1 = MINIMIZE;

    static const Direction OBJECTIVE_DIRECTION_2 = MINIMIZE;


    static double objectiveFunction1(Solution *solution) {
        return f((RealArraySolution *)solution);
    }

    static double objectiveFunction2(Solution *solution) {
        return g((RealArraySolution *)solution)*h((RealArraySolution *)solution);
    }
};

/** Restriction based on the lower and higher bounds setted on the RealArraySolution class
 *  see RealArraySolution::lowerBounds and RealArraySolution::higherBounds
 */
class RealRestriction : public Restriction {
private:


public:

    RealRestriction() {}

    virtual ~RealRestriction() {}

protected:

    virtual bool evaluate(Solution *solution) {
        RealArraySolution *realSolution = (RealArraySolution *) solution;

        for (int i = 0; i < RealArraySolution::getSize(); i++)
            if (!checkLowerBound(realSolution->get(i), i) || !checkHigherBound(realSolution->get(i), i))
                return false;

        return true;
    }

    virtual void normalize(Solution *solution) {
        RealArraySolution *realSolution = (RealArraySolution *) solution;
        for (int i = 0; i < RealArraySolution::getSize(); i++)
            if (!checkLowerBound(realSolution->get(i), i) || !checkHigherBound(realSolution->get(i), i))
                realSolution->set(i, RealRestriction::normalize(realSolution->get(i), i));
    }

private:

    static bool checkLowerBound(double value, int idx) {
        if (RealArraySolution::getLowerBounds() != NULL && value < RealArraySolution::getLowerBounds()[idx])
            return false;

        else
            return true;
    }

    static bool checkHigherBound(double value, int idx) {
        if (RealArraySolution::getHigherBounds() != NULL && value > RealArraySolution::getHigherBounds()[idx])
            return false;

        else
            return true;
    }


    static double normalize(double value, int idx) {

        while (!checkLowerBound(value, idx) || !checkHigherBound(value, idx)) {

            if (!checkHigherBound(value, idx)) {
                value = RealArraySolution::getHigherBounds()[idx];

            } else {
                value = RealArraySolution::getLowerBounds()[idx];

            }
        }

        return value;
    }

};

#endif
